package se.webbzon.boltzmann.game.object.npc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import se.webbzon.boltzmann.battle.environment.BattleEnvironment;
import se.webbzon.boltzmann.battle.factory.property.PlayerBPF;
import se.webbzon.boltzmann.battle.group.BattleGroup;
import se.webbzon.boltzmann.battle.object.npc.BattleNPC;
import se.webbzon.boltzmann.battle.property.BattlePropertyFactory;
import se.webbzon.boltzmann.battle.round.BattleRound;
import se.webbzon.boltzmann.battle.round.BattleRoundListener;
import se.webbzon.boltzmann.debug.Debug;
import se.webbzon.boltzmann.effect.Effect;
import se.webbzon.boltzmann.effect.ScreenTransitionEffect;
import se.webbzon.boltzmann.game.character.CharacterException;
import se.webbzon.boltzmann.game.npc.GameNPC;
import se.webbzon.boltzmann.game.npc.StalkingDuty;
import se.webbzon.boltzmann.game.npc.StandingDuty;
import se.webbzon.boltzmann.game.npc.WalkingDuty;
import se.webbzon.boltzmann.game.object.player.AbstractPlayerCharacter;
import se.webbzon.boltzmann.npc.NPCDuty;
import se.webbzon.boltzmann.player.Player;
import se.webbzon.boltzmann.timer.Timer;
import se.webbzon.boltzmann.timer.TimerListener;
import se.webbzon.boltzmann.world.BattleWorldLoader;
import se.webbzon.boltzmann.world.WorldLoaderException;
import se.webbzon.oschi01.collision3d.CollisionCube3D;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldobject.WorldObject;

public class HostileNPC extends GameNPC {
	
	// The maximal range of the battle trigger
	private static final double battleRange = 24;
	
	// The battle environment associated with this hostile non-player character
	private final BattleEnvironment benv;
	
	// A list of all battle property factories associated with this hostile
	// non-player character
	private final List<BattlePropertyFactory> battlePropertyFactories;
	
	// The name of the battle world area associated with this hostile
	// non-player character
	private final String battleWorldAreaName;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates an hostile non-player character. **/
	public HostileNPC(	String characterTextureSetName,
						BattleEnvironment benv,
						HostileAI ai,
						List<BattlePropertyFactory> battlePropertyFactories,
						String battleWorldAreaName) 
								throws CharacterException {
		super(WorldObject.onStep,characterTextureSetName);
		this.benv = benv;
		this.battlePropertyFactories = battlePropertyFactories;
		this.battleWorldAreaName = battleWorldAreaName;
		setMask(new CollisionCube3D());
		setSolid(true);
		final NPCDuty d;
		switch (ai) {
			case STANDING: d = new StandingDuty(this); break;
			case PATROL_SMALL: d = new StalkingDuty(this,64,64,8); break;
			case PATROL_MEDIUM: d = new StalkingDuty(this,192,192,8); break;
			case PATROL_HUGE: d = new StalkingDuty(this,320,320,8); break;
			case BLIND_SMALL: d = new WalkingDuty(this,64,8); break;
			case BLIND_MEDIUM: d = new WalkingDuty(this,192,8); break;
			case BLIND_HUGE: d = new WalkingDuty(this,320,8); break;
			default: d = new StandingDuty(this,0);
		}
		assignDuty(d);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	@Override public boolean isHostile() {
		return true;
	}
	
	/** Returns a collection of all battle property factories associated
	 * with this hostile non-player game character. **/
	public List<BattlePropertyFactory> getBattlePropertyFactories() {
		return Collections.unmodifiableList(battlePropertyFactories);
	}
	
	@Override public void onStep(WorldArea world) {
		super.onStep(world);
		Collection<AbstractPlayerCharacter> players = getPlayers();
		AbstractPlayerCharacter battleWith = null;
		synchronized (players) {
			// Check if any player is within range
			for (AbstractPlayerCharacter player : players)
				if (!player.isInvulnerable() && distanceTo(player) <= battleRange) {
					battleWith = player;
					break;
				}
		}
		// Starts a battle with the selected player
		if (battleWith != null) {
			battleWith.setInvulnerable(90);
			Object blocker = battleWith.containBlocker();
			Effect transition = 
					new ScreenTransitionEffect(	
							ScreenTransitionEffect.CLOCKWISE + 
							ScreenTransitionEffect.HORIZONTAL + 
							ScreenTransitionEffect.VERTICAL,
							2,0.5,"transition_star",30);
			transition.launch(world);
			Timer timer = new Timer(30);
			timer.addListener(new TimerListenerImpl(battleWith,blocker,world));
			timer.start(world);
			
		}
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Enters a battle with a given player from a given game world area. **/
	private final void enterBattle(Player player, WorldArea gameWorld) {
		try {
			ArrayList<BattleGroup> battleGroups = new ArrayList<BattleGroup>();
			
			// Add the player to the battle
			BattleGroup friendlyGroup = new BattleGroup(0,0);
			BattlePropertyFactory spf = new PlayerBPF(benv,player);
			friendlyGroup.addParticipant(new BattleNPC(spf.makeNew(null),friendlyGroup));
			battleGroups.add(friendlyGroup);
			
			// Add all the enemies to the battle
			BattleGroup hostileGroup = new BattleGroup(1,1);
			for (BattlePropertyFactory factory : battlePropertyFactories) {
				hostileGroup.addParticipant(
						new BattleNPC(factory.makeNew(null),hostileGroup));
			}
			battleGroups.add(hostileGroup);
			
			// Add a listener used return to the game world
			final ArrayList<BattleRoundListener> listeners = 
					new ArrayList<BattleRoundListener>();
			listeners.add(new BattleRoundListenerImpl(gameWorld,this));
			
			// Load the battle
			final BattleWorldLoader loader = 
					new BattleWorldLoader(	benv,battleWorldAreaName,player,
											battleGroups,listeners);
			final WorldArea battleWorld = loader.load();
			
			gameWorld.gotoWorldArea(battleWorld);
		} catch (CharacterException | WorldLoaderException e) {
			Debug.report(e);
		}
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** An implementation of a battle round listener. **/
	private static final class BattleRoundListenerImpl implements BattleRoundListener {

		// The game world to return to after the battle
		private final WorldArea gameWorld;
		
		// The enemy involved in the battle
		private final HostileNPC enemy;
		
		// The battle world in which the listener is used
		private WorldArea battleWorld;
		
		/*============================================================
		Constructors
		============================================================*/
		
		/** Creates a new battle round implementation. **/
		public BattleRoundListenerImpl(	WorldArea gameWorld, 
										HostileNPC enemy) {
			this.gameWorld = gameWorld;
			this.enemy = enemy;
		}
		
		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void roundStart(BattleRound battleRound) {};
		
		@Override public void roundComplete(BattleRound battleRound) {};

		@Override public void groupStart(BattleRound battleRound, BattleGroup group) {};
		
		@Override public void groupComplete(BattleRound battleRound, BattleGroup group) {};

		@Override public void turnStart(BattleRound battleRound, BattleNPC participant) {};

		@Override public void turnComplete(BattleRound battleRound, BattleNPC participant) {};

		@Override public void battleStart(BattleRound battleRound, WorldArea world) {
			battleWorld = world;
		}
		
		@Override public void battleComplete(BattleRound battleRound, int factionId) {
			gameWorld.destroyInstance(enemy);
			battleWorld.gotoWorldArea(gameWorld);
		}
		
	}
	
	/** An implementation of a TimerListener. **/
	private final class TimerListenerImpl implements TimerListener {
		
		// The player character associated with this timer listener
		private final AbstractPlayerCharacter player;
		
		// The player blocker
		private final Object playerBlocker;
		
		// The world area in which the timer was created
		private final WorldArea gameWorld;

		/*============================================================
		Constructors
		============================================================*/
		
		public TimerListenerImpl(	AbstractPlayerCharacter player,
									Object playerBlocker,
									WorldArea gameWorld) {
			this.player = player;
			this.playerBlocker = playerBlocker;
			this.gameWorld = gameWorld;
		}
		
		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void onAlarm(Timer timer, WorldArea world) {
			enterBattle(player.getPlayer(),gameWorld);
			player.releaseBlocker(playerBlocker);
		}
		
	}

}
